<?php
/**
 * Services_Omniture_DataInsertion
 *
 * PHP Version 5.1.0+
 *
 * @uses      Services_Omniture_Common
 * @package   Services_Omniture
 * @copyright 2008 Digg, Inc.
 * @author    Bill Shupp <bill@digg.com> 
 * @license   Digg Internal
 */

require_once 'HTTP/Request.php';
require_once 'Services/Omniture/Common.php';
require_once 'Services/Omniture/Exception.php';

/**
 * Services_Omniture_DataInsertion 
 * 
 * @uses      Services_Omniture_Common
 * @package   Services_Omniture
 * @copyright 2008 Digg, Inc.
 * @author    Bill Shupp <bill@digg.com> 
 * @license   Digg Internal
 */
class Services_Omniture_DataInsertion extends Services_Omniture_Common
{
    /**
     * Requestor 
     * 
     * @var string $requestor Method to use with sending requests
     */
    protected $requestor = null;

    /**
     * Options used by the requestor
     * 
     * @var array $options Options
     * @see setOption(), setOptions(), getOption()
     */
    protected $options = array(
        'uri'       => null,
        'timeout'   => 30,
        'userAgent' => 'Services_Omniture 0.1.0'
    );

    /**
     * Elements
     *
     * Array containing different variable names used by Omniture's
     * analytics.
     *
     * @var array $elements Possible data fields
     */
    protected static $elements = array(
        'campaign',          // Conversion campaign tracking code. 
        'channel',           // Channel string (e.g. Sports Section). 
        'currencyCode',      // Revenue currency code (e.g. USD). 
        'events',            // Conversion event list (e.g.  "purchase,event1"). 
        'linkName',          // Name of link. 
        'linkType',          // Type of link ("d", "e", or "o"). 
        'linkURL',           // HREF of link. 
        'pageName',          // Page Name. 
        'pageType',          // Page Type ("Error Page"). 
        'pageURL',           // Page URL (e.g.  http://www.example.com/index.html). 
        'products',          // Product list (e.g. "Sports;Ball;1;5.95"). 
        'purchaseID',        // Purchase ID number. 
        'reportSuiteID',     // Report suite ID(s) (s_account in JS) to which to 
                             // attribute the hit.
        'server',            // Server string. 
        'state',             // Conversion state string. 
        'zip',               // Conversion zip code. 
        'browserHeight',     // Browser height in pixels (e.g. 768). 
        'browserWidth',      // Browser width in pixels (e.g. 1024). 
        'colorDepth',        // Monitor color depth in bits (e.g. 24). 
        'connectionType',    // Visitor's connection type ("lan" or "modem"). 
        'cookiesEnabled',    // Y or N for if the visitor supports first-party 
                             // session cookies. 
        'homePage',          // Y or N for if the current page is the visitor's 
                             // homepage. 
        'javaEnabled',       // Y or N -- does the visitor have Java enabled. 
        'javascriptVersion', // JavaScript version (e.g. 1.3). 
        'referrer',          // URL for page referrer. 
        'resolution',        // Monitor resolution (e.g. 1024x768). 
        'scXmlVer',          // Omniture/DataInsertion XML request version number (e.g. 1.0). 
        'timezone',          // Visitor's time zone offset from GMT in hours
                             //  (e.g. -8). 
        'visitorID',         // Visitor ID number. 
        'plugins',           // Semicolon separated list of Netscape plug-in names. 
        'domain',            // Host visitor's IP address resolves to this 
        'ipAddress',         // Visitor's IP address 
        'language',          // Browser's supported language (e.g. "en-us") 
        'userAgent'          // User agent string (used to determine browser and OS)
    );

    /**
     * Limited Elements
     *
     * Variables used by Omniture that have a postfixing number that may
     * increment up until a certain amount. e.g. eVar1, eVar2, ... eVar50.
     *
     * @var array $limitedElements Limited elements
     */
    protected static $limitedElements = array(
        'prop' => 50,
        'eVar' => 50,
        'hier' => 5
    );

    /**
     * Data 
     * 
     * @var array $data Analytic data
     */
    protected $data = array();

    /**
     * __construct 
     * 
     * @param array  $options   Options
     * @param string $requestor Method of sending requests
     *
     * @return void
     */
    public function __construct(array $options, $requestor = 'HTTPRequest')
    {
        $this->requestor = $requestor;
        $this->setOptions($options);
    }

    /**
     * __set 
     * 
     * @param mixed $name Name of the variable 
     * @param mixed $val  Value of the variable 
     *
     * @return void
     */
    public function __set($name, $val)
    {
        $shortName = substr($name, 0, 4);
        if (array_key_exists($shortName, self::$limitedElements)) {
            preg_match('/[0-9]+/', $name, $id);
            $this->setLimitedElement($shortName, $id[0], $val);
            return;
        }

        if (!in_array($name, self::$elements)) {
            throw new Services_Omniture_Exception('Invalid element: ' . $name);
        }

        $this->data[$name] = $val;
    }

    /**
     * setLimitedElement 
     * 
     * @param mixed $name Name of limited element
     * @param int   $id   Id, the number value
     * @param mixed $val  Value of the limited element
     *
     * @return void
     */
    protected function setLimitedElement($name, $id, $val)
    {
        if ($id < 1 || $id > self::$limitedElements[$name]) {
            throw new Services_Omniture_Exception("Invalid $name id");
        }
        $this->data[$name . $id] = $val;
    }

    /**
     * sendRequest 
     * 
     * @return Simple_XML_Object XML response
     */
    public function sendRequest()
    {
        $this->validate();

        $requestor = $this->getRequestor();

        $dom = new DomDocument('1.0', 'UTF-8');
        $requestNode = $dom->createElement('request');
        var_dump($this->data);
        foreach ($this->data as $field => $value) {
            $element = $dom->createElement($field, $value);
            $requestNode->appendChild($element);
        }
        $dom->appendChild($requestNode);
        $xml = simplexml_import_dom($dom);

        $requestor->setBody($xml);
        $requestor->sendRequest();

        $code = $requestor->getResponseCode();
        $body = $requestor->getResponseBody();
        if (strncmp($code, 2, 1) !== 0) {
            throw new Services_Omniture_Exception($body, $code);
        }

        if (!strlen($body)) {
            throw new Services_Omniture_Exception(
                'Empty response was received from the API'
            );
        }

        libxml_use_internal_errors();
        $xml = simplexml_load_string($body);
        if (!$xml instanceof SimpleXMLElement) {
            throw new Services_Omniture_Exception(
                'Could not parse response received by the API: ' . $body
            );
        }

        if ((string)$xml != 'SUCCESS') {
            $reason = isset($xml->reason) ? (string)$xml->reason : 'Unknown';
            throw new Services_Omniture_Exception(
                'Invalid request.  Reason: ' . $reason
            );
        }
        return $xml;
    }

    /**
     * Validate
     * 
     * @return void
     * @throws Services_Omniture_Exception on invalid data
     */
    protected function validate()
    {
        $errors = array();
        if (!isset($this->data['reportSuiteID'])) {
            $errors[] = 'reportSuiteID';
        }
        if (!isset($this->data['pageName'])) {
            $errors[] = 'pageName';
        }

        if (!isset($this->data['visitorID']) && !isset($this->data['ipAddress'])) {
            $errors[] = 'visitorID or ipAddress';
        }

        if (count($errors)) {
            $message = "Missing required elements: " . implode(',', $errors);
            throw new Services_Omniture_Exception($message);
        }
    }

    /**
     * Sets an option.
     * 
     * @param mixed $option Option to set
     * @param mixed $value  Option value to set
     * 
     * @see setOptions(), $options
     * @return bool true if a valid option was passed, false otherwise
     */
    public function setOption($option, $value)
    {
        if (!array_key_exists($option, $this->options)) {
            return false;
        }
        $this->options[$option] = $value;
        return true;
    }

    /**
     * Overrides default options.
     *
     * Available options:
     * <pre>
     * timeout:   Timeout in seconds (default: 30)
     * userAgent: User-Agent string to be used.
     *            (default: 'ServicesOmniture/DataInsertion 0.1.0')
     * </pre>
     * 
     * @param array $options {@link $options} to set
     * 
     * @see setOption(), $options
     * @return void
     */
    public function setOptions(array $options)
    {
        foreach ($options as $key => $value) {
            $this->setOption($key, $value);
        }
    }

    /**
     * Retrieves an option from {@link $options}.
     * 
     * @param mixed $key Option to get
     * 
     * @return mixed Option value on success, null on failure
     */
    public function getOption($key)
    {
        if (array_key_exists($key, $this->options)) {
            return $this->options[$key];
        }
        return null;
    }

    /**
     * Instantiates the requestor.
     * 
     * @return Omniture_Request_Common A Omniture_Request_Common Object
     */
    protected function getRequestor()
    {
        $class = 'Services_Omniture_Request_' . $this->requestor;
        $file  = 'Services/Omniture/Request/' . $this->requestor . '.php';
        include_once $file;
        if (!class_exists($class)) {
            throw new Services_Omniture_Exception(
                'Class ' . $class . ' does not exist'
            );
        }
        return new $class($this->getOption('uri'), $this);
    }
}
?>
